#!/usr/bin/env python
"""
    renyare.xml.xslt
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This module implements XSLT transforming using different engines.
It also provides WSGI-middleware class, that acts as XSLT proxy, i.e. transforms
text-output of WSGI-applicaion or pre-parsed document, placd to environment

Example of using all of currently supported engines:
    >>> from os.path import dirname, realpath
    >>> from renyare.xml.xslt import *
    >>> xml = '''<?xml version="1.0"?><omg><lol wtf="PWND!"/></omg>'''
    >>> stylesheet = '''<?xml version="1.0" encoding="UTF-8"?>
    ... <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    ... <xsl:output indent="no" method="html" omit-xml-declaration="yes"/>
    ...     <xsl:template match="/omg"><xsl:value-of select="lol/@wtf"/></xsl:template>
    ... </xsl:stylesheet>'''
    >>> pi_href="file://%s" % realpath('test.xsl')
    >>> def transform_with(engine):
    ...     stylesheet_uri = "file://%s/test.xsl" % realpath(dirname(__file__)) # Only full path works with file URIs
    ...     engine.load_doc(data=xml)
    ...     engine.load_stylesheet(filename='test.xsl')
    ...     print engine.transform(serialize=True)
    ...     engine.load_doc(data=xml)
    ...     engine.load_stylesheet(uri=stylesheet_uri) # Just demonstrates loading from URI. Stylesheet doen't have to be reloaded on each transform
    ...     print engine.transform()
    >>> engines = [ LibxsltEngine, LxmlEngine, AmaraEngine ]
    >>> for e in engines:
    ...     print e.__name__
    ...     transform_with(e())
    LibxsltEngine
    <?xml version="1.0"?>
    PWND!
    <BLANKLINE>
    <?xml version="1.0"?>
    PWND!
    <BLANKLINE>
    LxmlEngine
    <?xml version="1.0"?>
    PWND!
    <BLANKLINE>
    <?xml version="1.0"?>
    PWND!
    <BLANKLINE>
    AmaraEngine
    <?xml version="1.0" encoding="UTF-8"?>
    PWND!
    <?xml version="1.0" encoding="UTF-8"?>
    PWND!
"""
from os.path import isfile, join
from urllib import pathname2url
from urlparse import urlparse, urlunparse, urljoin
from hashlib import md5

from ..wsgi.error import RenyareWarning, RenyareError
from ..util import from_environ
from .util import smart_base

response_environ = 'renyare.xml_response'
stylesheet_environ = 'renyare.stylesheet'
XSLT_NS = "http://www.w3.org/1999/XSL/Transform"
XSLT_NSP = 'xsl:'
ACCEPT_MIMES = ['text/xml', 'application/xml', 'application/xml+xslt']

class XsltProxyMiddleware(object):
    """
    Checks for browser support of parsing XML data with XSLT
    and makes parsing if it does not. Can also search skin directories for
    template overrides.
    To avoid conflicts between WSGI output (using `return` or `yield`) and
    parsed response, parsed response is ignored if any data is returned or
    yielded by application. In the case, when application have set the
    xml_response variable first, any WSGI output is ignored.
    >>> from sys import stdin, stdout, stderr
    >>> import wsgiref, wsgiref.util, wsgiref.validate, wsgiref.handlers
    >>> import lxml.etree as et
    >>> from cStringIO import StringIO
    >>> s = '''<?xml version="1.0" encoding="UTF-8"?>
    ...     <ren:page xmlns:ren="http://www.renyare.ru/NS/templating">
    ...     <ren:context>
    ...         <ren:root>.</ren:root>
    ...         <ren:path>/some/path/</ren:path>
    ...         <ren:static>/static/</ren:static>
    ...         <ren:skin>default</ren:skin>
    ...     </ren:context>
    ...     <ren:application>
    ...         <any>
    ...          <application>here</application>
    ...             or
    ...           <data>anywhere</data>
    ...         </any>
    ...     </ren:application>
    ...     </ren:page>'''
    >>> stylesheet = '''<?xml version="1.0" encoding="UTF-8"?>
    ... <xsl:stylesheet version="1.0"
    ...     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    ...     xmlns:ren="http://www.renyare.ru/NS/templating">
    ... <xsl:output indent="no" method="html" omit-xml-declaration="yes"/>
    ...     <xsl:template match="/ren:page">LOL can be <xsl:value-of select="ren:application/any/data/text()"/></xsl:template>
    ... </xsl:stylesheet>'''
    >>> def xml_app(environ, start_response):
    ...     environ['renyare.xml_response'] = et.fromstring(s)
    ...     environ['renyare.stylesheet'] = et.fromstring(stylesheet)
    ...     start_response('200 OK', [('Content-type', 'text/xml')])
    ...     return ['']
    >>> environ = {'QUERY_STRING':''}
    >>> wsgiref.util.setup_testing_defaults(environ)
    >>> app = XsltProxyMiddleware(xml_app, 'server', LxmlEngine)
    >>> app = wsgiref.validate.validator(app)
    >>> out = StringIO()
    >>> wsgiref.handlers.BaseCGIHandler(stdin, out, stderr, environ).run(app)
    >>> print repr(out.getvalue())
    'Status: 200 OK\\r\\nContent-type: text/html\\r\\n\\r\\nLOL can be anywhere\\n'
    """
    engine = None
    app_ct = ""
    def __init__(self, app, mode, engine,
                 forward_non_parsed=True,
                 response_mime='text/html'):
        """
        :param engine: - a class or string specifying XSLT-transformation engine
        :param mode: should be one of the following:
            'client' - for client-side parsing
            'server' - for server-side parsing
            'smart' - for automatic decision based on Accept HTTP header
        :param forward_non_parsed:param  - if application didn't provide parsed response in
            xml format, set this setting to True to forward response "as is" and
            False to parse the response and apply transformations.
        :param response_mime: - override MIME-type (Content-type header) for response
        Stylesheet should be specified in environment variable
        'renyare.stylesheet' in form of URL or preparsed document
        If no stylesheet specified, the middleware tries to take it from
        <?xml-stylesheet...?> processing instruction.
        """
        self.app = app
        self.engine_init = engine
        self.forward_non_parsed = forward_non_parsed
        self.response_mime = response_mime
        self.mode = mode

    def __call__(self, environ, start_response):
        self.environ = environ
        self.start_response = start_response
        self.local_transform = from_environ(environ, 'renyare.local_transform',
                                            detect_local_transform,
                                            environ,
                                            mode=self.mode)
        environ['renyare.require_parsed'] = True
        self.response = None
        have_output = False
        for data in self.app(environ, self.new_response):
            if response is None and self.forward_non_parsed:
                yield data
                have_output = True
        if not have_output and self.response is not None:
            self.engine = from_environ(environ, 'renyare.engine', initialize_engine, self.engine_init)
            if self.local_transform:
                yield self.server_transform()
            else:
                yield self.client_transform()

    def client_transform(self):
        if stylesheet_environ in self.environ:
            if isinstance(self.environ[stylesheet_environ], basestring):
                uri = self.environ[stylesheet_environ]
                if not urlparse(uri)[0] in ['http', 'https']:
                    raise RenyareError("Only http or https schemes are alllowed with client-side stylesheets. URI: %s" %uri)
                self.engine.add_pi(uri, self.engine.root2doc(self.response))
            else:
                raise NotImplementedError("Stylesheet embedding is not implemented yet")
        return self.engine.serialize(self.response)

    def server_transform(self):
        self.engine.load_doc(data=self.response)
        if stylesheet_environ in self.environ:
            if isinstance(self.environ[stylesheet_environ], basestring):
                self.engine.load_stylesheet(uri=self.environ[stylesheet_environ])
            else:
                self.engine.load_stylesheet(data=self.environ[stylesheet_environ])
        else:
            self.engine.load_stylesheet(from_pi=True)
        return self.engine.transform()

    def new_response(self, status, headers, exc_info=None):
        """Replaces content-type header with the correct one"""
        if response_environ in self.environ:
            self.response = self.environ[response_environ]
            if self.local_transform:
                for hdr in headers:
                    if hdr[0].lower() == 'content-type':
                        i = headers.index(hdr)
                headers[i] = ('Content-type', self.response_mime)
        self.start_response(status, headers, exc_info)

def detect_local_transform(environ, mode=None):
    """
    Tests request for HTTP Accept header and determines if xslt transformtaion
    can be made on client side. Returns found mimes array for local transform or
    `None` if nothing was found.
    :param accept_mimes: keyword can be specified to override mimes that mean allowed
    client-side transformation.
    """
    if mode is None:
        mode = environ['renyare.config']['renyare']['xslt_mode']
    if mode == 'client':
        return False
    elif mode == 'server':
        return True
    elif mode == 'smart':
        local_transform = None
        # RFC 2616 14.1 http://www.faqs.org/rfcs/rfc2616.html
        if not 'HTTP_ACCEPT' in environ:
            return True
        accept = environ['HTTP_ACCEPT'].split(';')[0].strip()
        accept = [ i for i in accept.split(',') ]
        xml_type = None
        types = [ mime for mime in ACCEPT_MIMES if mime in accept ]
        if len(types) > 0:
            environ['renyare._content_type'] = types[0]
            return False
        else:
            return True
    else:
        raise RenyareWarning("Unknown xslt transform mode '%s' supplied." % mode)

def add_template(template, environ, skinned=True):
    """
    Function for convenient adding of new templates.
    :param template: template path must be specified relatively to templates
    directory or as *absolute* URL
    :param skinned: parameter should be used to say if template must be skinned
    or not
    """
    # http://blogs.msdn.com/ie/archive/2006/12/06/file-uris-in-windows.aspx
    skin = None
    if skinned:
        skin = from_environ(environ, 'renyare.skin', set_skin, environ)
    templates = from_environ(environ, 'renyare._templates', list, [])
    local_transform = from_environ(environ, 'renyare.local_transform',
                                   detect_local_transform, environ)
    base = smart_base(local_transform, environ['renyare.paths']['templates'], environ['renyare.urls']['templates'], skin)
    abs_template = urljoin(base, template)
    templates.append(abs_template)


def combine_templates(environ):
    """
    Prepares the document for rendering:
        * join all stylesheets added with add_template into one stylesheet
        * put the document and stylesheet to environment
    """
    if 'renyare.stylesheet' in environ: # Skip any actions if variable was set before
        return
    if not 'renyare._templates' in environ:
        add_template(environ['renyare.config']['renyare']['default_stylesheet'].strip(), environ, skinned=False)
    templates = environ['renyare._templates']
    str = md5(''.join(templates)).hexdigest()+'.xsl'
    cache_dir = environ['renyare.paths']['public_cache']
    path = join(cache_dir, str)
    if not isfile(path):
        f = file(path, 'wb')
        engine = from_environ(environ, 'renyare.engine', initialize_engine,
                          environ['renyare.config']['renyare']['xslt_engine'])
        engine.load_stylesheet()
        environ['renyare.stylesheet'] = engine.stylesheet_doc
        for tmpl in templates:
            engine.include(tmpl)
        f.write(engine.serialize(engine.stylesheet_doc))
        f.close()
    local_transform = from_environ(environ, 'renyare.local_transform',
                                   detect_local_transform, environ)
    base = smart_base(local_transform, environ['renyare.paths']['public_cache'], environ['renyare.urls']['cache'])
    url = urljoin(base, str)
    environ['renyare.stylesheet'] = url

def set_skin(environ):
    s = environ['renyare.config']['renyare']['skin'].strip()
    return type(s)(s)

def initialize_engine(engine):
    """Setter function, initializes XSLT-engine by it's name or class"""
    global available_engines
    if isinstance(engine, basestring):
        if engine in available_engines:
            engine = available_engines[engine]
        else:
            raise RenyareError("%s engine is not available. Module not present?" % engine_name)
    engine = engine()
    return engine

def skin_template(paths, skin, template, mode):
    """
    Makes full paths from relative ones (specified in templates) according to
    parsing mode ('client' or 'server')
    """
    skinned_template = None
    non_skinned_template = None
    # Search the template paths for skin directory
    found_base = None
    for base in paths:
        skinned = os.path.join(base, skin, template)
        if skinned_template is None and os.path.isfile(skinned):
            skinned_template = template
            found_base = base
        non_skinned = os.path.join(base, 'default', template)
        if non_skinned_template is None and \
          skinned_template is None and \
          os.path.isfile(non_skinned):
            non_skinned_template = template
            found_base = base
    found_template = None
    if not skinned_template is None:
        found_template = skinned_template
    else:
        found_template = non_skinned_template
    if found_template is None:
        return None
    if mode == 'server':
        return os.path.join(found_base, skin, found_template)
    elif mode == 'client':
        return escape('/templates/%s/%s' % (skin, template))
    else:
        raise RenyareError("Unknown mode specified")

#############################################
available_engines = {}

class BaseEngine(object):
    """
    A Base class to create custom transforming engines.
    """
    def __init__(self):
        self._init()

    def _init(self):
        pass

    def load_doc(self, data=None, filename=None, uri=None, baseuri=None):
            self.doc = self._load_xml(data, filename, uri)

    def load_stylesheet(self, from_pi=False, data=None, filename=None, uri=None, params={},
                        output='html', encoding='UTF-8'):
        self._from_pi = from_pi
        self._params = params
        self.output = output
        self.encoding = encoding
        if not from_pi and (data is not None or filename is not None or uri is not None):
            self.stylesheet_doc = self._load_xml(data, filename, uri)
        elif (not from_pi and data is None and filename  is None and uri is None):
            self.stylesheet_doc = self._create_empty_stylesheet()

    def include(self, uri):
        self._append_include(uri)

    def _load_xml(self, data=None, filename=None, uri=None, baseuri=None):
        if data is not None:
            if isinstance(data, basestring):
                doc = self._load_xml_string(data, baseuri)
            else:
                doc = data
        elif filename is not None:
            doc = self._load_xml_file(filename)
        elif uri is not None:
            doc = self._load_xml_uri(uri)
        else:
            raise TypeError, "You must specify either data, filename or url"
        return doc

    def root2doc(self, root):
        """Finds root node of the document"""
        raise NotImplementedError, "This feature is not implemented yet. Sorry"

    def doc2root(self, doc):
        """Finds the the document object for the root node"""
        raise NotImplementedError, "This feature is not implemented yet. Sorry"

    def add_pi(self, uri, doc):
        """Add the <?xml-stylesheet...?> processing instruction to XML data"""

    def _process_stylesheet(self, from_pi):
        """Must return XML parsed from python string"""
        raise NotImplementedError, "This feature is not implemented yet. Sorry"

    def _load_xml_string(self, data, baseuri=None):
        """Must return XML parsed from python string"""
        raise NotImplementedError, "This feature is not implemented yet. Sorry"

    def _load_xml_file(self, filename):
        """Must return XML parsed from filename"""
        raise NotImplementedError, "This feature is not implemented yet. Sorry"

    def _load_xml_uri(self, uri):
        """Must return XML parsed from URI"""
        raise NotImplementedError, "This feature is not implemented yet. Sorry"

    def _create_empty_stylesheet(self, uri):
        """Must generate an empty stylesheet"""
        raise NotImplementedError, "This feature is not implemented yet. Sorry"

    def _append_include(self, uri):
        """Must append an include xslt tag to the stylesheet root"""
        raise NotImplementedError, "This feature is not implemented yet. Sorry"

    def transform(self, serialize=True):
        """Transforms a document and returns a string or object"""
        self._process_stylesheet(self._from_pi)
        doc = self._transform()
        if serialize:
            return self.serialize(doc)
        else:
            return doc

    def serialize(self, doc):
        """Must return a doc in string representaion. By default returns str(doc)"""
        return str(doc)

    def _transform(self):
        """Must return NON-SERIALIZED result of the transform"""
        raise NotImplementedError, "This feature is not implemented yet. Sorry"

try:
    import libxml2
    import libxslt
    available_engines['libxslt'] = True
except ImportError:
    pass

class LibxsltEngine(BaseEngine):
    """
    Transforms documents generated with libxml2 using libxslt
    """

    def _load_xml_string(self, data, baseuri=None):
        return libxml2.parseDoc(data)

    def _load_xml_file(self, filename):
        return libxml2.parseFile(filename)

    def _load_xml_uri(self, uri):
        return libxml2.parseFile(uri)

    def _process_stylesheet(self, from_pi):
        if from_pi:
            self.stylesheet = libxslt.loadStylesheetPI(self.doc)
        else:
            self.stylesheet = libxslt.parseStylesheetDoc(self.stylesheet_doc)

    def _transform(self):
        result = self.stylesheet.applyStylesheet(self.doc, self._params)
        return result

    def _create_empty_stylesheet(self):
        xml = libxml2.newDoc("1.0")
        sheet = xml.newChild(None, "stylesheet", None)
        self._ns = sheet.newNs(XSLT_NS, XSLT_NSP[:-1])
        sheet.setNs(self._ns)
        sheet.setProp('version', '1.0')
        # Generate output instructions
        out = sheet.newChild(self._ns, "output", None)
        if(self.output == 'xml'):
            out.setProp('method', 'xml')
            out.setProp('version', '1.0')
        elif(self.output == 'html'):
            out.setProp('method', 'html')
            out.setProp('version', '4.0')
            out.setProp('doctype-public', '-//W3C//DTD XHTML 1.0 Strict//EN')
            out.setProp('doctype-system', 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd')
        out.setProp('omit-xml-declaration', 'yes')
        out.setProp('encoding', self.encoding)
        out.setProp('indent', 'no')
        return xml

    def _append_include(self, uri):
        inc = self.doc2root(self.stylesheet_doc).newChild(self._ns, "include", None)
        inc.setProp('href', uri)
        return inc

    def add_pi(self, uri, doc):
        pi = doc.newDocPI('xml-stylesheet', 'type="text/xml" href="%s"'%uri)
        doc.addPrevSibling(pi)

    def root2doc(self, root):
        """Finds root node of the document"""
        return root.get_doc()

    def doc2root(self, doc):
        """Finds the the document object for the root node"""
        return doc.getRootElement()

if 'libxslt' in available_engines:
    available_engines['libxslt'] = LibxsltEngine

try:
    import lxml.etree as et
    from lxml.etree import XSLT
    available_engines['lxml'] = True
except ImportError:
    pass

class LxmlEngine(BaseEngine):
    """
    Transforms ElementTree documents using lxml internal libxslt-based engine
    """
    def _load_xml_string(self, data, baseuri=None):
        return et.fromstring(data)

    def _load_xml_file(self, filename):
        return et.parse(filename)

    _load_xml_uri = _load_xml_file

    def _process_stylesheet(self, from_pi):
        if from_pi:
            ss_doc = None
        else:
            ss_doc = self.stylesheet_doc
        self.stylesheet = XSLT(ss_doc, **self._params)

    def serialize(self, doc):
        if hasattr(doc, 'getroottree'):
            ret = doc.getroottree()
        else:
            ret = doc
        return et.tostring(ret)

    def _transform(self):
        result = self.stylesheet(self.doc)
        return result

    def _create_empty_stylesheet(self):
        self._nsp = '{%s}'%XSLT_NS
        nsmap = { XSLT_NSP[:-1] : XSLT_NS}
        sheet = et.Element(self._nsp+"stylesheet", nsmap=nsmap)
        sheet.set('version', '1.0')
        # Generate output instructions
        out = et.Element(self._nsp+"output")
        if(self.output == 'xml'):
            out.set('method', 'xml')
            out.set('version', '1.0')
        elif(self.output == 'html'):
            out.set('method', 'html')
            out.set('version', '4.0')
            out.set('doctype-public', '-//W3C//DTD XHTML 1.0 Strict//EN')
            out.set('doctype-system', 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd')
        out.set('omit-xml-declaration', 'yes')
        out.set('encoding', self.encoding)
        out.set('indent', 'no')
        sheet.append(out)
        return sheet

    def _append_include(self, uri):
        inc = et.SubElement(self.stylesheet_doc, self._nsp+"include")
        inc.set('href', uri)
        return inc

    def add_pi(self, uri, doc):
        root = self.doc2root(doc)
        root.addprevious(et.PI('xml-stylesheet', 'type="text/xml" href="%s"'%uri))

    def root2doc(self, root):
        """Finds root node of the document"""
        return root.getroottree()

    def doc2root(self, doc):
        """Finds the the document object for the root node"""
        return doc.getroot()

if 'lxml' in available_engines:
    available_engines['lxml'] = LxmlEngine

try:
    import Ft.Xml
    from Ft.Xml.Domlette import NonvalidatingReader, implementation
    from Ft.Xml.Xslt import Processor
    from Ft.Lib import Uri
    available_engines['amara'] = True
except ImportError:
    pass

class AmaraEngine(BaseEngine):
    """
    Transforms documents using Amara 4Suite XML engine
    """
    def _init(self):
        self._proc = Processor.Processor()
        self._reader = NonvalidatingReader
        self._generated = False

    def _load_xml_string(self, data, baseuri=None):
        if baseuri is None:
            baseuri = 'urn:dummy'
        return Ft.Xml.InputSource.DefaultFactory.fromString(data, baseuri)

    def _load_xml_file(self, filename):
        file_uri = Uri.OsPathToUri(filename)
        return self._load_xml_uri(file_uri)

    def _load_xml_uri(self, uri):
        return Ft.Xml.InputSource.DefaultFactory.fromUri(uri)

    def _process_stylesheet(self, from_pi):
        if not from_pi:
            if self._generated:
                self._proc.appendStylesheetNode(self.stylesheet_doc)
            else:
                self._proc.appendStylesheet(self.stylesheet_doc)

    def _transform(self):
        result = self._proc.run(self.doc)
        return result

    def _create_empty_stylesheet(self):
        xml = implementation.createRootNode('urn:fake')
        sheet = xml.createElementNS(XSLT_NS, XSLT_NSP+'stylesheet')
        xml.appendChild(sheet)
        sheet.setAttributeNS(None, 'version', '1.0')
        # Generate output instructions
        out = xml.createElementNS(XSLT_NS, XSLT_NSP+'output')
        if(self.output == 'xml'):
            out.setAttributeNS(None, 'method', 'xml')
            out.setAttributeNS(None, 'version', '1.0')
        elif(self.output == 'html'):
            out.setAttributeNS(None, 'method', 'html')
            out.setAttributeNS(None, 'version', '4.0')
            out.setAttributeNS(None, 'doctype-public', '-//W3C//DTD XHTML 1.0 Strict//EN')
            out.setAttributeNS(None, 'doctype-system', 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd')
        out.setAttributeNS(None, 'omit-xml-declaration', 'yes')
        out.setAttributeNS(None, 'encoding', self.encoding)
        out.setAttributeNS(None, 'indent', 'no')
        sheet.appendChild(out)
        self._generated = True
        return xml

    def _append_include(self, uri):
        inc = self.stylesheet_doc.createElementNS(XSLT_NS, XSLT_NSP+"include")
        inc.setAttributeNS(None, 'href', uri)
        inc = self.doc2root(self.stylesheet_doc).appendChild(inc)
        self._generated = True
        return inc

    def add_pi(self, uri, doc):
        pi = doc.createProcessingInstruction('xml-stylesheet', 'type="text/xml" href="%s"'%uri)
        doc.insertBefore(pi, doc.documentElement)

    def root2doc(self, root):
        """Finds root node of the document"""
        return root.ownerDocument

    def doc2root(self, doc):
        """Finds the the document object for the root node"""
        return doc.documentElement

if 'amara' in available_engines:
    available_engines['amara'] = AmaraEngine

def test_engines():
    from os.path import dirname, realpath
    xml = '''<?xml version="1.0"?><omg><lol wtf="PWND!"/></omg>'''
    stylesheet = '''<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/><xsl:preserve-space elements=""/>
<xsl:template match="/omg"><xsl:value-of select="lol/@wtf"/></xsl:template>
</xsl:stylesheet>'''
    for eng in available_engines.values():
        print "Testing %s engine" % eng.__name__
        stylesheet_uri = "file://%s/test.xsl" % realpath(dirname(__file__)) # Only full path works with file URIs
        e = eng()
        e.load_doc(data=xml) # Loading from string
        assert hasattr(e, 'doc'), "Error loading from string"
        e.load_doc(data=e.doc) # Loading from pre-parsed document
        assert hasattr(e, 'doc'), "Error loading from pre-parsed document"
        e.load_stylesheet(filename='test.xsl') # Loading from filename
        assert hasattr(e, 'stylesheet_doc'), "Error loading stylesheet from filename"
        e = eng()
        e.load_doc(data=xml)
        e.add_pi(stylesheet_uri)
        e.load_stylesheet(from_pi=True) # Loading from document PI
        assert hasattr(e, 'stylesheet_doc'), "Error loading stylesheet from document PI"
        result = e.transform()
        print repr(result.strip())
        s = '<?xml version="1.0"%s?>\nPWND!'
        e = eng()
        e.load_doc(data=xml)
        e.load_stylesheet(uri=stylesheet_uri) # Loading from URI
        assert hasattr(e, 'stylesheet_doc'), "Error loading stylesheet from uri"
        result = e.transform()
        print repr(result.strip())
        s = '<?xml version="1.0"%s?>\nPWND!'
        assert result.strip() == s % '' or result.strip() == s % ' encoding="UTF-8"', "Transformation error"
        e = eng()
        e.load_doc(data=xml)
        e.load_stylesheet(output="html")
        assert hasattr(e, 'stylesheet_doc'), "Error loading stylesheet from uri"
        e.include(stylesheet_uri)
        result = e.transform()
        s = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"'\
        ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\nPWND!'
        assert result.strip() == s or result.strip() == 'PWND!', "Transformation error"
        print "---Tests OK"

if __name__ == "__main__":
    import nose
    print "Available engines:"
    for e in available_engines:
        print e
